﻿namespace MicroCloud.Identity
{
    /// <summary>
    /// 业务实现：身份认证模块
    /// </summary>
    public partial class IdentityService : IIdentityContract
    {
        //字段
        private readonly IServiceProvider _provider;

        #region "属性"
        #region "获取 当前用户的基本功能"
        /// <summary>
        /// 获取 当前用户的基本功能
        /// </summary>
        protected IPrincipal Principal => _provider.GetService<IPrincipal>();
        #endregion

        #region "获取 角色管理器"
        /// <summary>
        /// 获取 角色管理器
        /// </summary>
        protected RoleManager<Role> RoleManager => _provider.GetService<RoleManager<Role>>();
        #endregion
        #region "获取 用户管理器"
        /// <summary>
        /// 获取 用户管理器
        /// </summary>
        protected UserManager<User> UserManager => _provider.GetService<UserManager<User>>();
        #endregion
        #region "获取 登录管理器"
        /// <summary>
        /// 获取 登录管理器
        /// </summary>
        protected SignInManager<User> SignInManager => _provider.GetService<SignInManager<User>>();
        #endregion

        #region "获取 用户证照仓储模型"
        /// <summary>
        /// 获取 用户证照仓储模型
        /// </summary>
        protected IRepository<UserPortfolio, long> UserPortfolioRepository => _provider.GetService<IRepository<UserPortfolio, long>>();
        #endregion
        #region "获取 用户详细仓储模型"
        /// <summary>
        /// 获取 用户详细仓储模型
        /// </summary>
        protected IRepository<UserDetail, long> UserDetailRepository => _provider.GetService<IRepository<UserDetail, long>>();
        #endregion
        #region "获取 用户登录仓储模型"
        /// <summary>
        /// 获取 用户登录仓储模型
        /// </summary>
        protected IRepository<UserLogin, long> UserLoginRepository => _provider.GetService<IRepository<UserLogin, long>>();
        #endregion
        #region "获取 用户登录日志仓储模型"
        /// <summary>
        /// 获取 用户登录日志仓储模型
        /// </summary>
        protected IRepository<LoginLog, long> LoginLogRepository => _provider.GetService<IRepository<LoginLog, long>>();
        #endregion
        #region "获取 用户角色仓储模型"
        /// <summary>
        /// 获取 用户角色仓储模型
        /// </summary>
        protected IRepository<UserRole, long> UserRoleRepository => _provider.GetService<IRepository<UserRole, long>>();
        #endregion
        #region "获取 用户组织机构映射仓储模型"
        /// <summary>
        /// 获取 用户组织机构映射仓储模型
        /// </summary>
        protected IRepository<UserOrganization, long> UserOrganizationRepository => _provider.GetService<IRepository<UserOrganization, long>>();
        #endregion

        #region "获取 角色组织机构映射仓储模型"
        /// <summary>
        /// 获取 角色组织机构映射仓储模型
        /// </summary>
        protected IRepository<RoleOrganization, long> RoleOrganizationRepository => _provider.GetService<IRepository<RoleOrganization, long>>();
        #endregion

        #region "获取 组织机构仓储"
        /// <summary>
        /// 获取 组织机构仓储
        /// </summary>
        protected IRepository<Organization, long> OrganizationRepository => _provider.GetService<IRepository<Organization, long>>();
        #endregion
        #region "获取 提供查询组织机构存储中的组织机构"
        /// <summary>
        /// 获取 提供查询组织机构存储中的组织机构
        /// </summary>
        protected IQueryableOrganizationStore<Organization> QueryableOrganizationStore => _provider.GetService<IQueryableOrganizationStore<Organization>>();
        #endregion

        #region "获取 事件总线"
        /// <summary>
        /// 获取 事件总线
        /// </summary>
        protected IEventBus EventBus => _provider.GetService<IEventBus>();
        #endregion
        #region "获取 当前用户对象"
        /// <summary>
        /// 获取 当前用户对象
        /// </summary>
        protected IPrincipal CurrentUser => _provider.GetService<IPrincipal>();
        #endregion
        #region "获取 序列化值的分布式缓存"
        /// <summary>
        /// 获取 序列化值的分布式缓存
        /// </summary>
        protected IDistributedCache Cache => _provider.GetService<IDistributedCache>();
        #endregion
        #region "获取 Http上下文访问器"
        /// <summary>
        /// 获取 Http上下文访问器
        /// </summary>
        protected IHttpContextAccessor HttpContextAccessor => _provider.GetService<IHttpContextAccessor>();
        #endregion
        #region "获取 日志对象"
        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger<IdentityService> Logger => _provider.GetService<ILogger<IdentityService>>();
        #endregion

        #region "获取 JwtBearer服务"
        /// <summary>
        /// 获取 JwtBearer服务
        /// </summary>
        protected IJwtBearerService JwtBearerService => _provider.GetService<IJwtBearerService>();
        #endregion
        #region "获取 Http客户端工厂"
        /// <summary>
        /// 获取 Http客户端工厂
        /// </summary>
        protected IHttpClientFactory HttpClientFactory => _provider.GetService<IHttpClientFactory>();
        #endregion

        #region "获取 角色信息查询数据集"
        /// <summary>
        /// 获取 角色信息查询数据集
        /// </summary>
        public IQueryable<Role> Roles => RoleManager.Roles;
        #endregion
        #region "获取 用户信息查询数据集"
        /// <summary>
        /// 获取 用户信息查询数据集
        /// </summary>
        public IQueryable<User> Users => UserManager.Users;
        #endregion
        #region "获取 组织机构信息查询数据集"
        /// <summary>
        /// 获取 组织机构信息查询数据集
        /// </summary>
        public IQueryable<Organization> Organizations => OrganizationRepository.QueryAsNoTracking();
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个业务实现：身份认证模块的新实例"
        /// <summary>
        /// 初始化一个业务实现：身份认证模块 <see cref="IdentityService"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public IdentityService(IServiceProvider provider)
        {
            _provider = provider;
        }
        #endregion

        #endregion

        #region "方法"
        #region "注册账号"
        /// <summary>
        /// 注册账号
        /// </summary>
        /// <param name="registerType">注册类型</param>
        /// <param name="dto">注册信息</param>
        /// <param name="emailConfirmed">电子邮箱确认</param>
        /// <param name="phoneConfirmed">手机确认</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> Register(UserRegisterType registerType, RegisterDto dto
            , bool emailConfirmed = false, bool phoneConfirmed = false)
        {
            Check.NotNull(dto, nameof(dto));
            if ((registerType == UserRegisterType.Both || registerType == UserRegisterType.Email)
                && dto.Email.IsMissing())
            {
                return OperationResult.Error<User>(I18N.T("电子邮箱不能为空"));
            }
            if ((registerType == UserRegisterType.Both || registerType == UserRegisterType.PhoneNumber)
                && dto.PhoneNumber.IsMissing())
            {
                return OperationResult.Error<User>(I18N.T("手机号码不能为空"));
            }

            User user = new()
            {
                UserName = dto.UserName,
                NickName = dto.NickName ?? dto.UserName,
                Email = dto.Email,
                EmailConfirmed = emailConfirmed,
                PhoneNumber = dto.PhoneNumber,
                PhoneConfirmed = phoneConfirmed,
            };
            IdentityResult iResult = dto.Password.IsNullOrEmpty() ? await UserManager.CreateAsync(user) : await UserManager.CreateAsync(user, dto.Password);
            if (!iResult.Succeeded)
            {
                return iResult.ToOperationResult(user);
            }
            UserDetail detail = new() { UserId = user.Id, RegisterIp = dto.RegisterIp, RegisterType = registerType };
            var result = await UserDetailRepository.InsertAsync(detail);
            if (!result.Succeeded)
            {
                return result.ToOperationResult(user);
            }
            UserPortfolio portfolio = new() { UserId = user.Id, Avatar = StaticHelper.GetImgBase64String(ImageName.CatAvatar) };
            result = await UserPortfolioRepository.InsertAsync(portfolio);
            if (!result.Succeeded)
            {
                return result.ToOperationResult(user);
            }
            //组织机构
            if (!dto.OrganizationId.IsMissing())
            {
                //验证登录的组织机构信息
                var orgKey = dto.OrganizationId.CastTo<long>(default);
                var org = QueryableOrganizationStore.Organizations.FirstOrDefault(o => o.Id.Equals(orgKey));
                if (org == null)
                {
                    throw new InvalidOperationException(I18N.T("组织机构 {0} 不存在", dto.OrganizationId));
                }
                if (!org.EnableLogin)
                {
                    throw new InvalidOperationException(I18N.T("组织机构 {0} 不可登录", dto.OrganizationId));
                }
                if (org.IsLocked)
                {
                    throw new InvalidOperationException(I18N.T("组织机构 {0} 已锁定", dto.OrganizationId));
                }
                if (!org.IsDefault)
                {
                    var userOrg = new UserOrganization() { UserId = user.Id, OrganizationId = org.Id };
                    await UserOrganizationRepository.InsertAsync(userOrg);
                }
            }

            //触发注册成功事件
            if (result.Succeeded)
            {
                RegisterEventData eventData = new() { RegisterDto = dto, User = user };
                await EventBus?.PublishAsync(eventData);

                return OperationResult.Success(user);
            }

            return result.ToOperationResult<User>();
        }
        #endregion
        #region "使用账号登录"
        /// <summary>
        /// 使用账号登录
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> Login(LoginDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            var userDictData = await FindUserByAccount(dto.Account);
            User user = userDictData.Value;
            if (user == null)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 不存在", dto.Account));
            }
            if (user.IsLocked)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 已锁定", dto.Account));
            }
            //如果组织机构有值，验证值有效性
            if (!dto.OrganizationId.IsMissing())
            {
                //验证登录的组织机构信息
                var orgKey = dto.OrganizationId.CastTo<long>(default);
                var org = QueryableOrganizationStore.Organizations.FirstOrDefault(o => Equals(o.Id, orgKey));
                if (org == null)
                {
                    return OperationResult.Error<User>(I18N.T("组织机构 {0} 不存在", dto.OrganizationId));
                }
                //用户组织结构
                List<string> organizationIds = new();
                var userOrganizations = await UserOrganizationStore.GetOrganizationsAsync(user);
                if (userOrganizations?.Count > 0)
                {
                    organizationIds = organizationIds.Union(userOrganizations).ToList();
                }
                //角色组织机构
                foreach (var userRole in user.UserRoles)
                {
                    var roleOrganizations = await RoleOrganizationStore.GetOrganizationsAsync(userRole.Role);
                    if (roleOrganizations?.Count > 0)
                    {
                        organizationIds = organizationIds.Union(roleOrganizations).ToList();
                    }
                }
                //验证
                if (!organizationIds.Contains(dto.OrganizationId))
                {
                    throw new Exception(I18N.T("用户 {0} 不允许登录组织 {1}", user.UserName, dto.OrganizationId));
                }
            }

            SignInResult signInResult = await SignInManager.CheckPasswordSignInAsync(user, dto.Password, true);
            OperationResult<User> result = ToOperationResult(signInResult, userDictData);
            if (!result.Succeeded)
            {
                return result;
            }
            Logger.LogDebug("用户 {userId}-{userName}[{userNickname}] 使用账号登录系统成功", user.Id, user.UserName, user.NickName);

            //触发登录成功事件
            LoginEventData loginEventData = new() { LoginDto = dto, User = user };
            await EventBus?.PublishAsync(loginEventData);
            //触发功能权限缓存刷新事件
            FunctionAuthCacheRefreshEventData functionAuthCacheRefreshEventData = new() { UserIds = new[] { user.Id.ToString() } };
            await EventBus?.PublishAsync(functionAuthCacheRefreshEventData);

            return result;
        }
        #endregion

        #region "使用手机登录"
        /// <summary>
        /// 使用手机登录
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> PhoneLogin(LoginDto dto)
        {
            //验证提交的信息
            Check.NotNull(dto, nameof(dto));
            Check.NotNullOrEmpty(dto.Account, nameof(dto.Account));

            var user = UserManager.Users.FirstOrDefault(m => m.PhoneNumber == dto.Account);
            if (user == null)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 不存在", dto.Account));
            }
            if (user.IsLocked)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 已锁定", dto.Account));
            }
            //如果组织机构有值，验证值有效性
            if (!dto.OrganizationId.IsMissing())
            {
                //验证登录的组织机构信息
                var orgKey = dto.OrganizationId.CastTo<long>(default);
                var org = QueryableOrganizationStore.Organizations.FirstOrDefault(o => o.Id.Equals(orgKey));
                if (org == null)
                {
                    return OperationResult.Error<User>(I18N.T("组织机构 {0} 不存在", dto.OrganizationId));
                }
                var userOrganizations = await UserOrganizationStore.GetOrganizationsAsync(user);
                if (userOrganizations == null || userOrganizations.Count <= 0 || !userOrganizations.Contains(dto.OrganizationId))
                {
                    throw new Exception(I18N.T("用户 {0} 不允许登录组织 {1}", user.UserName, dto.OrganizationId));
                }
            }

            //如果手机没有激活，则自动激活
            if (!user.PhoneConfirmed)
            {
                user.PhoneConfirmed = true;
                await UserManager.UpdateAsync(user);
            }
            Logger.LogDebug("用户 {userId}-{userName}[{userNickname}] 使用手机登录系统成功", user.Id, user.UserName, user.NickName);

            //触发登录成功事件
            LoginEventData loginEventData = new() { LoginDto = dto, User = user };
            await EventBus?.PublishAsync(loginEventData);
            //触发功能权限缓存刷新事件
            FunctionAuthCacheRefreshEventData functionAuthCacheRefreshEventData = new() { UserIds = new[] { user.Id.ToString() } };
            await EventBus?.PublishAsync(functionAuthCacheRefreshEventData);

            return OperationResult.Success(user);
        }
        #endregion

        #region "使用授权码登录"
        /// <summary>
        /// 使用授权码登录
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> AuthorizationLogin(LoginDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            var userDictData = await FindUserByAccount(dto.Account);
            User user = userDictData.Value;
            if (user == null)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 不存在", dto.Account));
            }
            if (user.IsLocked)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 已锁定", dto.Account));
            }
            if (user.PasswordHash != dto.Password)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 授权码无效", dto.Account));
            }
            //如果组织机构有值，验证值有效性
            if (!dto.OrganizationId.IsMissing())
            {
                //验证登录的组织机构信息
                var orgKey = dto.OrganizationId.CastTo<long>(default);
                var org = QueryableOrganizationStore.Organizations.FirstOrDefault(o => Equals(o.Id, orgKey));
                if (org == null)
                {
                    return OperationResult.Error<User>(I18N.T("组织机构 {0} 不存在", dto.OrganizationId));
                }
                //用户组织结构
                List<string> organizationIds = new();
                var userOrganizations = await UserOrganizationStore.GetOrganizationsAsync(user);
                if (userOrganizations?.Count > 0)
                {
                    organizationIds = organizationIds.Union(userOrganizations).ToList();
                }
                //角色组织机构
                foreach (var userRole in user.UserRoles)
                {
                    var roleOrganizations = await RoleOrganizationStore.GetOrganizationsAsync(userRole.Role);
                    if (roleOrganizations?.Count > 0)
                    {
                        organizationIds = organizationIds.Union(roleOrganizations).ToList();
                    }
                }
                //验证
                if (!organizationIds.Contains(dto.OrganizationId))
                {
                    throw new Exception(I18N.T("用户 {0} 不允许登录组织 {1}", user.UserName, dto.OrganizationId));
                }
            }

            Logger.LogDebug("用户 {userId}-{userName}[{userNickname}] 使用授权码登录系统成功", user.Id, user.UserName, user.NickName);

            //触发登录成功事件
            LoginEventData loginEventData = new() { LoginDto = dto, User = user };
            await EventBus?.PublishAsync(loginEventData);
            //触发功能权限缓存刷新事件
            FunctionAuthCacheRefreshEventData functionAuthCacheRefreshEventData = new() { UserIds = new[] { user.Id.ToString() } };
            await EventBus?.PublishAsync(functionAuthCacheRefreshEventData);

            return OperationResult.Success(user);
        }
        #endregion

        #region "创建第三方票证信息"
        /// <summary>
        /// 创建第三方票证信息
        /// </summary>
        /// <param name="dto">第三方身份获取TokenDTO</param>
        /// <returns></returns>
        public virtual async Task<OperationResult<UserLoginInfoEx>> CreateTicketAsync(OAuth2TokenDto dto)
        {
            OperationResult<UserLoginInfoEx> result = OperationResult.Success<UserLoginInfoEx>();
            switch (dto.Provider)
            {
                case OAuth2Provider.WeChat:
                    result = await CreateTicketByWeChatAsync(dto.AppId, dto.AppSecret, dto.Code);
                    break;
                case OAuth2Provider.DingTalk:
                    result = await CreateTicketByDingTalkAsync(dto.AppId, dto.AppSecret, dto.Code);
                    break;
                case OAuth2Provider.TikTok:
                    result = await CreateTicketByTikTokAsync(dto.AppId, dto.AppSecret, dto.Code);
                    break;
                case OAuth2Provider.QQ:
                case OAuth2Provider.Microsoft:
                case OAuth2Provider.GitHub:
                    result = OperationResult.Error<UserLoginInfoEx>(I18N.T("非常抱歉亲，QQ、微软账户、GitHub账户的【创建第三方票证信息】功能尚在开发中，请耐心等待。"));
                    break;
            }
            return result;
        }
        #endregion

        #region "使用第三方用户信息进行OAuth2登录"
        /// <summary>
        /// 使用第三方用户信息进行OAuth2登录
        /// </summary>
        /// <param name="loginInfo">第三方用户信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult> LoginOAuth2(UserLoginInfoEx loginInfo)
        {
            var result = await SignInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, true);
            if (!result.Succeeded)
            {
                string cacheId = await SetLoginInfoEx(loginInfo);
                if (result.IsLockedOut)
                {
                    return OperationResult.Error((object)cacheId, I18N.T("登录失败：用户已被锁定。"));
                }
                if (result.IsNotAllowed)
                {
                    return OperationResult.Error((object)cacheId, I18N.T("登录失败：用户已被禁止登录。"));
                }
                if (result.RequiresTwoFactor)
                {
                    return OperationResult.Error((object)cacheId, I18N.T("登录失败：用户登录需要双因素身份验证。"));
                }
                return OperationResult.Error((object)cacheId, I18N.T("登录失败"));
            }

            User user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
            if (user.IsLocked)
            {
                return OperationResult.Error(I18N.T("登录失败：用户已被冻结。"));
            }
            return OperationResult.Success((object)user, I18N.T("登录成功"));
        }
        #endregion

        #region "预设置第三方用户信息进行OAuth2绑定"
        /// <summary>
        /// 预设置第三方用户信息进行OAuth2绑定
        /// </summary>
        /// <param name="loginInfo">第三方用户信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult> BindOAuth2Preset(UserLoginInfoEx loginInfo)
        {
            Check.NotNull(loginInfo, nameof(loginInfo));
            Check.NotNullOrEmpty(loginInfo.LoginProvider, nameof(loginInfo.LoginProvider));
            Check.NotNullOrEmpty(loginInfo.ProviderKey, nameof(loginInfo.ProviderKey));

            string cacheId = await SetLoginInfoEx(loginInfo);
            return OperationResult.Success((object)cacheId);
        }
        #endregion
        #region "使用第三方用户信息进行OAuth2绑定"
        /// <summary>
        /// 使用第三方用户信息进行OAuth2绑定
        /// </summary>
        /// <param name="loginInfo">第三方用户信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult> BindOAuth2(UserLoginInfoEx loginInfo)
        {
            Check.NotNull(loginInfo, nameof(loginInfo));
            Check.NotNullOrEmpty(loginInfo.ProviderKey, nameof(loginInfo.ProviderKey));

            UserLoginInfoEx existLoginInfoEx = await GetLoginInfoEx(loginInfo.ProviderKey);
            if (existLoginInfoEx == null)
            {
                return OperationResult.Error(I18N.T("无法找到相应的第三方登录信息"));
            }

            if (Principal?.Identity.IsAuthenticated != true)
            {
                return OperationResult.Error(I18N.T("绑定失败：用户未登录。"));
            }
            long userId = Principal.Identity.GetUserId<long>();
            User user = await UserManager.FindByIdAsync(userId.ToString());
            if (user == null)
            {
                return OperationResult.Error(I18N.T("绑定失败：用户不存在。"));
            }
            if (user.IsLocked)
            {
                return OperationResult.Error(I18N.T("绑定失败：用户已被冻结。"));
            }

            var result = await CreateOrUpdateUserLogin(user, existLoginInfoEx);
            if (!result.Succeeded)
            {
                return result.ToOperationResult();
            }
            return OperationResult.Success((object)user, I18N.T("绑定成功"));
        }
        #endregion

        #region "登录并绑定现有账号"
        /// <summary>
        /// 登录并绑定现有账号
        /// </summary>
        /// <param name="loginInfoEx">第三方登录信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> LoginBind(UserLoginInfoEx loginInfoEx)
        {
            UserLoginInfoEx existLoginInfoEx = await GetLoginInfoEx(loginInfoEx.ProviderKey);
            if (existLoginInfoEx == null)
            {
                return OperationResult.Error<User>(I18N.T("无法找到相应的第三方登录信息"));
            }

            LoginDto loginDto = new() { Account = loginInfoEx.Account, Password = loginInfoEx.Password };
            OperationResult<User> loginResult = await Login(loginDto);
            if (!loginResult.Succeeded)
            {
                return loginResult;
            }

            User user = loginResult.Data;
            IdentityResult result = await CreateOrUpdateUserLogin(user, existLoginInfoEx);
            if (!result.Succeeded)
            {
                return result.ToOperationResult(user);
            }
            return OperationResult.Success(user);
        }
        #endregion
        #region "一键创建新用户并登录"
        /// <summary>
        /// 一键创建新用户并登录
        /// </summary>
        /// <param name="loginInfoEx">第三方登录信息</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult<User>> LoginOneKey(UserLoginInfoEx loginInfoEx)
        {
            UserLoginInfoEx existLoginInfoEx = await GetLoginInfoEx(loginInfoEx.ProviderKey);
            if (existLoginInfoEx == null)
            {
                return OperationResult.Error<User>(I18N.T("无法找到相应的第三方登录信息"));
            }
            IdentityResult result;
            User user = await UserManager.FindByLoginAsync(existLoginInfoEx.LoginProvider, existLoginInfoEx.ProviderKey);
            if (user == null)
            {
                user = new User()
                {
                    UserName = loginInfoEx.Account.IsMissing() ? $"{existLoginInfoEx.LoginProvider}_{existLoginInfoEx.ProviderKey}" : loginInfoEx.Account,
                    NickName = existLoginInfoEx.ProviderDisplayName,
                    EmailConfirmed = false,
                    PhoneConfirmed = false,
                };
                result = loginInfoEx.Password.IsMissing() ? await UserManager.CreateAsync(user) : await UserManager.CreateAsync(user, loginInfoEx.Password);
                if (!result.Succeeded)
                {
                    return result.ToOperationResult(user);
                }
                UserDetail detail = new() { UserId = user.Id, RegisterIp = existLoginInfoEx.RegisterIp, RegisterType = UserRegisterType.Neither };
                var result2 = await UserDetailRepository.InsertAsync(detail);
                if (!result2.Succeeded)
                {
                    return result2.ToOperationResult(user);
                }
                UserPortfolio portfolio = new() { UserId = user.Id, Avatar = existLoginInfoEx.AvatarUrl };
                result2 = await UserPortfolioRepository.InsertAsync(portfolio);
                if (!result2.Succeeded)
                {
                    return result2.ToOperationResult(user);
                }
            }

            result = await CreateOrUpdateUserLogin(user, existLoginInfoEx);
            if (!result.Succeeded)
            {
                return result.ToOperationResult(user);
            }
            return OperationResult.Success(user);
        }
        #endregion

        #region "验证登录"
        /// <summary>
        /// 验证登录
        /// </summary>
        /// <param name="dto">登录信息</param>
        /// <returns>JSON操作结果</returns>
        public virtual async Task<OperationResult<User>> CheckLogin(LoginDto dto)
        {
            Check.NotNull(dto, nameof(dto));

            var userDictData = await FindUserByAccount(dto.Account);
            User user = userDictData.Value;
            if (user == null)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 不存在", dto.Account));
            }
            if (user.IsLocked)
            {
                return OperationResult.Error<User>(I18N.T("用户 {0} 已锁定", dto.Account));
            }
            SignInResult signInResult = await SignInManager.CheckPasswordSignInAsync(user, dto.Password, true);
            OperationResult<User> result = ToOperationResult(signInResult, userDictData);
            if (!result.Succeeded)
            {
                return result;
            }
            Logger.LogDebug("用户 {userId}-{userName}[{userNickname}] 使用账号验证解锁成功", user.Id, user.UserName, user.NickName);

            return OperationResult.Success(user); ;
        }
        #endregion
        #region "账号退出"
        /// <summary>
        /// 账号退出
        /// </summary>
        /// <param name="isToken">是否token认证</param>
        /// <returns>业务操作结果</returns>
        public virtual async Task<OperationResult> Logout(bool isToken = true)
        {
            var identity = CurrentUser.Identity;
            long userId = CurrentUser.Identity.GetUserId<long>();
            var clientId = identity.GetClientId();

            await JwtBearerService.RemoveAudienceCache(userId.ToString(), clientId);

            if (!isToken)
            {
                await SignInManager.SignOutAsync();
            }
            Logger.LogDebug("用户 {userId} 登出系统", userId);

            //触发登出成功事件
            var logoutEventData = new LogoutEventData() { UserId = userId };
            await EventBus.PublishAsync(logoutEventData);

            return OperationResult.Success();
        }
        #endregion

        #endregion

        #region "受保护方法"
        #region "创建或更新用户登录"
        /// <summary>
        /// 创建或更新用户登录
        /// </summary>
        /// <param name="user"></param>
        /// <param name="loginInfoEx"></param>
        /// <returns></returns>
        protected virtual async Task<IdentityResult> CreateOrUpdateUserLogin(User user, UserLoginInfoEx loginInfoEx)
        {
            if (user.Portfolio != null && string.IsNullOrEmpty(user.Portfolio.Avatar) && !string.IsNullOrEmpty(loginInfoEx.AvatarUrl))
            {
                user.Portfolio.Avatar = loginInfoEx.AvatarUrl;
            }
            UserLogin userLogin = UserLoginRepository.GetFirst(m => m.LoginProvider == loginInfoEx.LoginProvider && m.ProviderKey == loginInfoEx.ProviderKey);
            if (userLogin == null)
            {
                userLogin = new UserLogin()
                {
                    LoginProvider = loginInfoEx.LoginProvider,
                    ProviderKey = loginInfoEx.ProviderKey,
                    DisplayName = loginInfoEx.ProviderDisplayName,
                    Avatar = loginInfoEx.AvatarUrl,
                    UserId = user.Id
                };
                await UserLoginRepository.InsertAsync(userLogin);
            }
            else
            {
                userLogin.UserId = user.Id;
                await UserLoginRepository.UpdateAsync(userLogin);
            }
            return IdentityResult.Success;
        }
        #endregion
        #region "依次按用户名、Email、手机查找用户"
        /// <summary>
        /// 依次按用户名、Email、手机查找用户
        /// </summary>
        /// <param name="account">账号</param>
        /// <returns></returns>
        protected virtual async Task<KeyValuePair<UserSignInType, User>> FindUserByAccount(string account)
        {
            User user = await UserManager.FindByNameAsync(account);
            if (user != null)
            {
                return new KeyValuePair<UserSignInType, User>(UserSignInType.Account, user);
            }
            if (account.IsEmail())
            {
                user = await UserManager.FindByEmailAsync(account);
                if (user != null)
                {
                    return new KeyValuePair<UserSignInType, User>(UserSignInType.Email, user);
                }
            }
            if (account.IsMobileNumber())
            {
                user = UserManager.Users.FirstOrDefault(m => m.PhoneNumber == account);
                if (user != null)
                {
                    return new KeyValuePair<UserSignInType, User>(UserSignInType.PhoneNumber, user);
                }
            }
            return new KeyValuePair<UserSignInType, User>(UserSignInType.Unknown, user);
        }
        #endregion
        #region "返回一个操作结果"
        /// <summary>
        /// 返回一个操作结果
        /// </summary>
        /// <param name="result">登录结果数据</param>
        /// <param name="userKeyValue">用户键值数据</param>
        /// <returns></returns>
        protected virtual OperationResult<User> ToOperationResult(SignInResult result, KeyValuePair<UserSignInType, User> userKeyValue)
        {
            var user = userKeyValue.Value;
            var signInType = userKeyValue.Key;
            if (result.IsNotAllowed)
            {
                if (UserManager.Options.SignIn.RequireConfirmedEmail && !user.EmailConfirmed)
                {
                    Logger.LogWarning("用户 {user} 因邮箱未验证而不允许登录", user.Id);
                    return OperationResult.Error<User>(I18N.T("用户邮箱未验证，请到邮箱收邮件进行确认后再登录。"));
                }
                if (UserManager.Options.SignIn.RequireConfirmedPhoneNumber && !user.PhoneConfirmed)
                {
                    Logger.LogWarning("用户 {user} 因手机号未验证而不允许登录", user.Id);
                    return OperationResult.Error<User>(I18N.T("用户手机号未验证，请接收手机验证码验证之后再登录。"));
                }
                return OperationResult.Error<User>(I18N.T("用户未满足登录条件，不允许登录。"));
            }
            if (result.IsLockedOut)
            {
                Logger.LogWarning("用户 {user} 因密码错误次数过多被锁定，解锁时间：{lockoutEnd}。", user.Id, user.LockoutEnd);
                return OperationResult.Error<User>(I18N.T("用户因密码错误次数过多而被锁定{0}分钟，请稍后重试。", UserManager.Options.Lockout.DefaultLockoutTimeSpan.TotalMinutes));
            }
            if (result.RequiresTwoFactor)
            {
                return OperationResult.Error<User>(I18N.T("用户登录需要二次验证"));
            }
            if (!result.Succeeded)
            {
                return OperationResult.Error<User>(I18N.T("用户名或密码错误，剩余{0}次机会。", UserManager.Options.Lockout.MaxFailedAccessAttempts - user.AccessFailedCount));
            }
            //加入的自定义验证逻辑
            if (signInType == UserSignInType.Email && !user.EmailConfirmed)
            {
                return OperationResult.Error<User>(I18N.T("用户邮箱未验证，请到邮箱收邮件进行确认后再登录。"));
            }
            if (signInType == UserSignInType.PhoneNumber && !user.PhoneConfirmed)
            {
                Logger.LogWarning("用户 {user} 因手机未验证而不允许登录", user.Id);
                return OperationResult.Error<User>(I18N.T("用户手机号未验证，请接收手机验证码验证之后再登录。"));
            }
            //加入的自定义验证逻辑END
            return OperationResult.Success(user);
        }
        #endregion

        #region "设置第三方用户登录信息"
        /// <summary>
        /// 设置第三方用户登录信息
        /// </summary>
        /// <param name="loginInfo"></param>
        /// <returns></returns>
        protected virtual async Task<string> SetLoginInfoEx(UserLoginInfoEx loginInfo)
        {
            string cacheId = Guid.NewGuid().ToString("N");
            string key = $"Identity_UserLoginInfoEx_{cacheId}";
            await Cache.SetAsync(key, loginInfo, 60 * 5);
            return cacheId;
        }
        #endregion
        #region "获取第三方用户登录信息"
        /// <summary>
        /// 获取第三方用户登录信息
        /// </summary>
        /// <param name="cacheId"></param>
        /// <returns></returns>
        protected virtual async Task<UserLoginInfoEx> GetLoginInfoEx(string cacheId)
        {
            string key = $"Identity_UserLoginInfoEx_{cacheId}";
            return await Cache.GetAsync<UserLoginInfoEx>(key);
        }
        #endregion

        #region "创建微信票证信息"
        /// <summary>
        /// 创建微信票证信息
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        protected virtual async Task<OperationResult<UserLoginInfoEx>> CreateTicketByWeChatAsync(string appId, string appSecret, string code)
        {
            //通过code获取access_token
            var address = QueryHelpers.AddQueryString(WeChatDefaults.TokenEndpoint, new Dictionary<string, string>()
            {
                ["appid"] = appId,
                ["secret"] = appSecret,
                ["code"] = code,
                ["grant_type"] = "authorization_code",
                // ["redirect_uri"] = redirectUri
            });
            using var client = HttpClientFactory.CreateClient();
            var response = await client.GetAsync(address);
            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索访问令牌时出错"));
            }
            var payload = JObject.Parse(responseContentJson);
            if (!string.IsNullOrEmpty(payload.Value<string>("errcode")))
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索访问令牌时出错"));
            }
            var accessToken = payload.Value<string>("access_token");
            var expiresIn = payload.Value<long>("expires_in");
            var refreshToken = payload.Value<string>("refresh_token");
            var openId = payload.Value<string>("openid");
            var scope = payload.Value<string>("scope");
            var unionId = payload.Value<string>("unionid");

            //从远程服务器创建票证
            address = QueryHelpers.AddQueryString(WeChatDefaults.UserInformationEndpoint, new Dictionary<string, string>
            {
                ["access_token"] = accessToken,
                ["openid"] = openId
            });
            response = await client.GetAsync(address);
            responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
            }
            payload = JObject.Parse(responseContentJson);
            if (!string.IsNullOrEmpty(payload.Value<string>("errcode")))
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
            }

            var userInfoDto = new WeChatUserInfoDto(payload);

            UserLoginInfoEx userInfoEx = new(OAuth2Provider.WeChat.ToString(), userInfoDto.UnionId, userInfoDto.NickName)
            {
                AvatarUrl = userInfoDto.HeadImgUrl.IsMissing() ? StaticHelper.GetImgBase64String(ImageName.CatAvatar) : userInfoDto.HeadImgUrl,
            };
            return OperationResult.Success(userInfoEx);
        }
        #endregion
        #region "创建钉钉票证信息"
        /// <summary>
        /// 创建钉钉票证信息
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        protected virtual async Task<OperationResult<UserLoginInfoEx>> CreateTicketByDingTalkAsync(string appId, string appSecret, string code)
        {
            var tmpAuthCode = code;
            var timestamp = GetTimeStamp();
            // 根据timestamp, appSecret计算签名值
            var signature = Hash_hmac2(timestamp.ToString(), appSecret);

            var address = QueryHelpers.AddQueryString(DingTalkDefaults.UserInformationEndpoint, new Dictionary<string, string>
            {
                ["accessKey"] = appId,
                ["timestamp"] = timestamp.ToString(),
                ["signature"] = signature
            });

            var bodyData = $"{{\"tmp_auth_code\":\"{tmpAuthCode}\"}}";
            using var client = HttpClientFactory.CreateClient();
            var response = await client.PostAsync(address, new StringContent(bodyData));
            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
            }

            var payload = JObject.Parse(responseContentJson);
            if (payload.Value<string>("errcode") != "0")
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
                //throw new HttpRequestException("检索用户信息时出错");
            }

            var userInfo = payload.Value<JObject>("user_info");
            var unionId = userInfo.Value<string>("unionid");
            var openId = userInfo.Value<string>("openid");
            var nickName = userInfo.Value<string>("nick");

            UserLoginInfoEx userInfoEx = new(OAuth2Provider.DingTalk.ToString(), unionId, nickName)
            {
                AvatarUrl = StaticHelper.GetImgBase64String(ImageName.UserAvatat),
            };
            return OperationResult.Success(userInfoEx);
        }
        #endregion
        #region "创建抖音票证信息"
        /// <summary>
        /// 创建抖音票证信息
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="code"></param>
        /// <returns></returns>
        protected virtual async Task<OperationResult<UserLoginInfoEx>> CreateTicketByTikTokAsync(string appId, string appSecret, string code)
        {
            //通过code获取access_token
            var address = QueryHelpers.AddQueryString(TikTokDefaults.TokenEndpoint, new Dictionary<string, string>()
            {
                ["client_key"] = appId,
                ["client_secret"] = appSecret,
                ["code"] = code,
                ["grant_type"] = "authorization_code",
            });

            var client = HttpClientFactory.CreateClient();
            var response = await client.GetAsync(address);
            string responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索访问令牌时出错"));
            }

            var payload = JObject.Parse(responseContentJson).Value<JObject>("data");
            if (payload.Value<string>("error_code") != "0")
            {
                Logger.LogError("检索访问令牌时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索访问令牌时出错"));
            }
            var accessToken = payload.Value<string>("access_token");
            var expiresIn = payload.Value<long>("expires_in");
            var refreshToken = payload.Value<string>("refresh_token");
            var refreshExpiresIn = payload.Value<long>("refresh_expires_in");
            var openId = payload.Value<string>("open_id");
            var scope = payload.Value<string>("scope");

            //从远程服务器创建票证
            address = QueryHelpers.AddQueryString(TikTokDefaults.UserInformationEndpoint, new Dictionary<string, string>
            {
                ["access_token"] = accessToken,
                ["open_id"] = openId
            });

            response = await client.GetAsync(address);
            responseContentJson = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
                //throw new HttpRequestException("检索用户信息时出错");
            }

            payload = JObject.Parse(responseContentJson);
            if (payload.Value<string>("error_code") != "0")
            {
                Logger.LogError("检索用户配置文件时出错：远程服务器返回了具有以下负载的{Status}响应：{Headers}{Body}.",
                                /* Status: */ response.StatusCode,
                                /* Headers: */ response.Headers.ToString(),
                                /* Body: */ responseContentJson);
                return OperationResult.Error<UserLoginInfoEx>(I18N.T("检索用户信息时出错"));
                //throw new HttpRequestException("检索用户信息时出错");
            }
            //error_code Integer     optional
            //description String optional
            //open_id String  用户在当前应用的唯一标识 optional
            //union_id String  用户在当前开发者账号下的唯一标识（未绑定开发者账号没有该字段）	optional
            //nickname    String optional
            //avatar String      optional
            //city    String optional
            //province String      optional
            //country String optional
            //gender EnumGender  性别: `0` -未知 `1` -男性 * `2` -女性    optional
            //e_account_role  EnumEAccountRole 类型: `EAccountM` -普通企业号 `EAccountS` -认证企业号 * `EAccountK` -品牌企业号
            var open_id = payload.Value<string>("open_id");
            var union_id = payload.Value<string>("union_id");
            union_id ??= open_id;
            var nickname = payload.Value<string>("nickname");
            var avatar = payload.Value<string>("avatar");

            UserLoginInfoEx userInfoEx = new(OAuth2Provider.TikTok.ToString(), union_id, nickname)
            {
                AvatarUrl = avatar.IsMissing() ? StaticHelper.GetImgBase64String(ImageName.CatAvatar) : avatar,
            };
            return OperationResult.Success(userInfoEx);
        }
        #endregion

        #endregion

        #region  "私有方法"
        #region "获取时间戳"
        /// <summary>
        /// 获取时间戳
        /// </summary>
        /// <returns></returns>
        private static long GetTimeStamp()
        {
            //TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            //var timestamp = Convert.ToInt64(ts.TotalMilliseconds).ToString();

            TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalMilliseconds);
        }
        #endregion
        #region "签名算法"
        /// <summary>
        /// 签名算法
        /// </summary>
        /// <param name="message"></param>
        /// <param name="secret"></param>
        /// <returns></returns>
        private static string Hash_hmac2(string message, string secret)
        {
            secret ??= "";
            var encoding = new System.Text.ASCIIEncoding();
            byte[] keyByte = encoding.GetBytes(secret);
            byte[] messageBytes = encoding.GetBytes(message);
            using (var hmacsha256 = new HMACSHA256(keyByte))
            {
                byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
                return Convert.ToBase64String(hashmessage);
            }
        }
        #endregion

        #endregion

    }
}
